﻿using DigitalQuran.Statistic;
using System;
using System.Collections.Generic;

namespace DigitalQuran.Numerology {

    /// <summary>
    /// Collection of Numerology System. Responsible for loading of Numerology Systems
    /// </summary>
    public sealed class NumerologyEngine : Dictionary<string, NumerologySystem> {
        public const string AllNumerologySystems = null;
        
        /// <summary>
        /// Creates new instance
        /// </summary>
        public NumerologyEngine() {
            Options = new NumerologyOptions();
        }

        /// <summary>
        /// Additional Numerology Sytem options used to adjust values
        /// </summary>
        public NumerologyOptions Options { get; private set; }

        string activeSystemName = "Primes";
        /// <summary>
        /// Gets the Active Numerology System
        /// </summary>
        public NumerologySystem ActiveSystem {
            get {
                NumerologySystem s;
                if (this.TryGetValue(activeSystemName, out s)) {
                    return s;
                }

                return null;
            }
        }

        /// <summary>
        /// Updates the Numerology System for sepcified Text mode and text
        /// </summary>
        /// <param name="textMode">Name of text mode</param>
        /// <param name="qObject">Quran Object whose statistic will be calculated</param>        
        public void UpdateNumerologySystems(string textMode, string systemName, IQuranObject qObject, SortMethod sortMethod, SortOrder sortOrder) {
            NumerologySystem s;
            if (Client.Instance != null && this.TryGetValue(systemName, out s)) {

                var letterStat = Client.Instance.Statistics.CalculateLetterStatistics(qObject, textMode); // update letter statistics dictionary
                letterStat.Sort(sortMethod, sortOrder);

                this.activeSystemName = s.UniqueName;

                // update numerology systems
                s.Update(letterStat, textMode);
            }
        }

        public void LoadSystems() {

            this.Add("Linear", new Systems.Linear());
            this.Add("Primes", new Systems.Prime(true));
            this.Add("Composites", new Systems.Composite());
            this.Add("Frequency Descending", new Systems.FrequencyDesending());

            if (Globals.Edition != Edition.Lite) {
                this.Add("Odd", new Systems.Odd());
                this.Add("Even", new Systems.Even());

                this.Add("Additive Primes", new Systems.AdditivePrimes(true));
                this.Add("Pure Primes", new Systems.PurePrimes(true));
                
                this.Add("Mersenne", new Systems.MersennePrimes());

                this.Add("Additive Composites", new Systems.AdditiveComposite());
                this.Add("Pure Composites", new Systems.PureComposite());

                this.Add("Fibonacci", new Systems.Fibonaccis());
            }

            if (Globals.Edition == Edition.Research) {
                this.Add("Primes Excluding 1", new Systems.Prime(false));
                this.Add("Additive Primes Excluding 1", new Systems.AdditivePrimes(false));
                this.Add("Pure Primes Excluding 1", new Systems.PurePrimes(false));

                this.Add("Frequency Ascending", new Systems.FrequencyAsending());
                this.Add("Log 2", new Systems.Log2());
                this.Add("Log 10", new Systems.Log10());

                this.Add("Pivot Books", new Systems.PivotBook());
                this.Add("Pivot Chapter", new Systems.PivotChapter());
                this.Add("Pivot Chapter First Factors", new Systems.PivotChapterFirstFactors());
                this.Add("Pivot Chapter Second Factors", new Systems.PivotChapterSecondFactors());
                this.Add("Pivot Chapter Verses", new Systems.PivotChapterVerses());
                this.Add("Pivot Support Chapters", new Systems.PivotSupportChapters());

                foreach (byte n in Enum.GetValues(typeof(PolygonType))) {
                    this.Add(n + " Sided Polygon", new Systems.Polygons(n));
                    this.Add(n + " Sided Centered Polygon", new Systems.CenteredPolygons(n));
                }

                foreach (byte n in Enum.GetValues(typeof(PolyhexType))) {
                    this.Add(((PolyhexType)n).ToString(), new Systems.PolyhexNumbers(n));
                }

                this.Add("Tetahedrals", new Systems.Tetahedrals());
                this.Add("Pyramidals", new Systems.Pyramidals());
                this.Add("Cubics", new Systems.Cubics());
            }
        }

        /// <summary>
        /// Calculate Value of specific text using active system
        /// </summary>
        /// <param name="userText">Text whose value to calculate</param>
        /// <returns>Returns value of text</returns>
        public long CalculateValue(string userText) {
            if (string.IsNullOrEmpty(userText)) return 0;

            long value = 0;
            if (ActiveSystem.ToString().Contains("English"))
                userText = userText.ToUpper();

            try {
                if (Client.Instance != null) {
                    userText = Client.Instance.Simplifier.SimplifyText(userText.Replace(" ", "").Replace("\n", "").Replace("\n", ""),
                                        ActiveSystem.TextMode);

                    long l;
                    for (int i = 0; i < userText.Length; i++) {
                        if (ActiveSystem.TryGetValue(userText[i], out l))
                            value += l;
                    }
                }
            } catch {
                value = 0;
            }

            return value;
        }

        /// <summary>
        /// Calculate Value of specific word using active system
        /// </summary>
        /// <param name="word">Word whose value to calculate</param>        
        /// <returns>Returns value of word</returns>
        public long CalculateValue(Word word) {
            return CalculateValue(word, ActiveSystem);
        }

        /// <summary>
        /// Calculate Value of specific verse using active system
        /// </summary>
        /// <param name="verse">Verse whose value to calculate</param>        
        /// <returns>Returns value of verse</returns>
        public long CalculateValue(Verse verse) {
            return CalculateValue(verse, ActiveSystem);
        }

        /// <summary>
        /// Calculate Value of specific Chapter using active system
        /// </summary>
        /// <param name="chapter">Chapter whose value to calculate</param>        
        /// <returns>Returns value of chapter</returns>
        public long CalculateValue(Chapter chapter) {
            return CalculateValue(chapter, ActiveSystem);
        }

        /// <summary>
        /// Calcuate value of specific word in specific verse using active system
        /// </summary>
        /// <param name="verse">Verse having word in it</param>
        /// <param name="wordIndex">Index of word in verse</param>        
        /// <returns>Return value of word</returns>
        public long CalculateValue(Verse verse, int wordIndex) {
            return CalculateValue(verse, wordIndex, ActiveSystem);
        }

        /// <summary>
        /// Calcuates Value for specified letter in specific word of specific verse using active system
        /// </summary>
        /// <param name="verse">Verse having word in it</param>
        /// <param name="wordIndex">Index of word in verse</param>
        /// <param name="letterIndex">Index of letter in word</param>        
        /// <returns>Return value of letter</returns>
        public long CalculateValue(Verse verse, int wordIndex, int letterIndex) {
            return CalculateValue(verse, wordIndex, letterIndex, ActiveSystem);
        }

        /// <summary>
        /// Calcuates Value for specified letter in specific word of specific verse using specific system
        /// </summary>
        /// <param name="verse">Verse having word in it</param>
        /// <param name="wordIndex">Index of word in verse</param>
        /// <param name="letterIndex">Index of letter in word</param> 
        /// <param name="system">System to use</param>
        /// <returns>Return value of letter</returns>
        public long CalculateValue(Verse verse, int wordIndex, int letterIndex, NumerologySystem system) {
            if (verse == null) return 0;

            long value = 0;
            if (this.Count > 0) {
                if ((verse.Words.Count == 1) && (verse.Words[0].Letters.Count == 1))
                    // adjust value of verse
                    value += Options.AdjustVerseValue(verse);

                if ((wordIndex >= 0) && (wordIndex < verse.Words.Count)) {
                    Word word = verse.Words[wordIndex];
                    string wordText = word.ToString(system.TextMode);

                    if (word.Letters.Count == 1)
                        // adjust value of word
                        value += Options.AdjustWordValue(word);

                    if ((letterIndex >= 0) && (letterIndex < wordText.Length)) {
                        char character = wordText[letterIndex];
                        long l;

                        if (system.TryGetValue(character, out l)) {
                            value += l;

                            // adjust value of letter
                            value += Options.AdjustLetterValue(word, letterIndex);
                        }
                    }
                }
            }

            return value;
        }

        /// <summary>
        /// Calculate Value of specific word using specific system
        /// </summary>
        /// <param name="word">Word whose value to calculate</param>
        /// <param name="system">System ti use</param>
        /// <returns>Returns value of word</returns>
        public long CalculateValue(Word word, NumerologySystem system) {
            if (word == null) return 0;

            long value = 0;

            string wordText = word.ToString(system.TextMode);

            if (word.Verse.Words.Count == 1)
                // adjust value of verse
                value += Options.AdjustVerseValue(word.Verse);

            // adjust value of word
            value += Options.AdjustWordValue(word);

            long l;
            for (int letterIndex = 0; letterIndex < wordText.Length; letterIndex++) {
                char character = wordText[letterIndex];

                if (system.TryGetValue(character, out l)) {
                    value += l;

                    // adjust value of letter
                    value += Options.AdjustLetterValue(word, letterIndex);
                }
            }

            word.Value = value;
            return value;
        }

        /// <summary>
        /// Calculate Value of specific verse using specific system
        /// </summary>
        /// <param name="verse">Verse whose value to calculate</param>
        /// <param name="system">System to use</param>
        /// <returns>Returns value of verse</returns>
        public long CalculateValue(Verse verse, NumerologySystem system) {
            if (verse == null) return 0;

            long value = 0;
            if (Count > 0) {
                // adjust value of verse
                value += Options.AdjustVerseValue(verse);

                long l;
                foreach (Word word in verse.Words) {
                    string wordText = word.ToString(system.TextMode);

                    // adjust value of word
                    value += Options.AdjustWordValue(word);

                    for (int letterIndex = 0; letterIndex < wordText.Length; letterIndex++) {
                        char character = wordText[letterIndex];

                        if (system.TryGetValue(character, out l)) {
                            value += l;

                            // adjust value of letter
                            value += Options.AdjustLetterValue(word, letterIndex);
                        }
                    }
                }
            }

            verse.Value = value;
            return value;
        }

        /// <summary>
        /// Calculate Value of specific Chapter usng specific system
        /// </summary>
        /// <param name="chapter">Chapter whose value to calculate</param>
        /// <param name="system">System to Use</param>
        /// <returns>Returns value of chapter</returns>
        public long CalculateValue(Chapter chapter, NumerologySystem system) {
            if (chapter == null) return 0;
            long result = 0;

            result += Options.AdjustChapterValue(chapter);

            foreach (Verse verse in chapter.Verses) {
                verse.Value = CalculateValue(verse, system);
                result += verse.Value;
            }

            chapter.Value = result;
            return result;
        }

        /// <summary>
        /// Calcuate value of specific word in specific verse using specific system
        /// </summary>
        /// <param name="verse">Verse having word in it</param>
        /// <param name="wordIndex">Index of word in verse</param>
        /// <param name="system">System to use</param>
        /// <returns>Return value of word</returns>
        public long CalculateValue(Verse verse, int wordIndex, NumerologySystem system) {
            if (verse == null) return 0;

            long value = 0;

            if ((wordIndex >= 0) && (wordIndex < verse.Words.Count)) {
                Word word = verse.Words[wordIndex];
                string wordText = word.ToString(system.TextMode);

                for (int l = 0; l < wordText.Length; l++)
                    value += CalculateValue(verse, wordIndex, l, system);
            }

            return value;
        }

        /// <summary>
        /// Gets Maximum Verse Value
        /// </summary>
        public long MaxVerseValue {
            get {
                long max = 0;

                foreach (var verse in Book.Instance.Verses) {
                    if (verse.Value > max)
                        max = verse.Value;
                }

                return max;
            }
        }

        /// <summary>
        /// Gets Maximum Word Value
        /// </summary>
        public long MaxWordValue {
            get {
                long max = 0;

                foreach (var word in Book.Instance.Words) {
                    if (word.Value > max)
                        max = word.Value;
                }

                return max;
            }
        }

        /// <summary>
        /// Gets Maximum Letter Value
        /// </summary>
        public long MaxLetterValue {
            get {
                long max = 0;

                foreach (var word in Book.Instance.Words) {
                    foreach (var letter in word.Letters) {
                        if (letter.Value > max)
                            max = letter.Value;
                    }
                }

                return max;
            }
        }
    }
}